package org.andengine.util.modifier;

import org.andengine.util.modifier.IModifier.IModifierListener;


/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @param <T>
 * @author Nicolas Gramlich
 * @since 11:18:37 - 03.09.2010
 */
public class LoopModifier<T> extends BaseModifier<T> implements IModifierListener<T> {
    // ===========================================================
    // Constants
    // ===========================================================

    public static final int LOOP_CONTINUOUS = -1;

    // ===========================================================
    // Fields
    // ===========================================================
    private final float mDuration;
    private final IModifier<T> mModifier;
    private final int mLoopCount;
    private float mSecondsElapsed;
    private ILoopModifierListener<T> mLoopModifierListener;
    private int mLoop;

    private boolean mModifierStartedCalled;
    private boolean mFinishedCached;

    // ===========================================================
    // Constructors
    // ===========================================================

    public LoopModifier(final IModifier<T> pModifier) {
        this(pModifier, LoopModifier.LOOP_CONTINUOUS);
    }

    public LoopModifier(final IModifier<T> pModifier, final int pLoopCount) {
        this(pModifier, pLoopCount, null, (IModifierListener<T>) null);
    }

    public LoopModifier(final IModifier<T> pModifier, final int pLoopCount, final IModifierListener<T> pModifierListener) {
        this(pModifier, pLoopCount, null, pModifierListener);
    }

    public LoopModifier(final IModifier<T> pModifier, final int pLoopCount, final ILoopModifierListener<T> pLoopModifierListener) {
        this(pModifier, pLoopCount, pLoopModifierListener, (IModifierListener<T>) null);
    }

    public LoopModifier(final IModifier<T> pModifier, final int pLoopCount, final ILoopModifierListener<T> pLoopModifierListener, final IModifierListener<T> pModifierListener) {
        super(pModifierListener);

        this.assertNoNullModifier(pModifier);

        this.mModifier = pModifier;
        this.mLoopCount = pLoopCount;
        this.mLoopModifierListener = pLoopModifierListener;

        this.mLoop = 0;
        this.mDuration = (pLoopCount == LoopModifier.LOOP_CONTINUOUS) ? Float.POSITIVE_INFINITY : pModifier.getDuration() * pLoopCount; // TODO Check if POSITIVE_INFINITY works correct with i.e. SequenceModifier

        this.mModifier.addModifierListener(this);
    }

    protected LoopModifier(final LoopModifier<T> pLoopModifier) throws DeepCopyNotSupportedException {
        this(pLoopModifier.mModifier.deepCopy(), pLoopModifier.mLoopCount);
    }

    @Override
    public LoopModifier<T> deepCopy() throws DeepCopyNotSupportedException {
        return new LoopModifier<T>(this);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    public ILoopModifierListener<T> getLoopModifierListener() {
        return this.mLoopModifierListener;
    }

    public void setLoopModifierListener(final ILoopModifierListener<T> pLoopModifierListener) {
        this.mLoopModifierListener = pLoopModifierListener;
    }

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    @Override
    public float getSecondsElapsed() {
        return this.mSecondsElapsed;
    }

    @Override
    public float getDuration() {
        return this.mDuration;
    }

    @Override
    public float onUpdate(final float pSecondsElapsed, final T pItem) {
        if (this.mFinished) {
            return 0;
        } else {
            float secondsElapsedRemaining = pSecondsElapsed;

            this.mFinishedCached = false;
            while ((secondsElapsedRemaining > 0) && !this.mFinishedCached) {
                secondsElapsedRemaining -= this.mModifier.onUpdate(secondsElapsedRemaining, pItem);
            }
            this.mFinishedCached = false;

            final float secondsElapsedUsed = pSecondsElapsed - secondsElapsedRemaining;
            this.mSecondsElapsed += secondsElapsedUsed;
            return secondsElapsedUsed;
        }
    }

    @Override
    public void reset() {
        this.mFinished = false;
        this.mLoop = 0;
        this.mSecondsElapsed = 0;
        this.mModifierStartedCalled = false;

        this.mModifier.reset();
    }

    // ===========================================================
    // Methods
    // ===========================================================

    @Override
    public void onModifierStarted(final IModifier<T> pModifier, final T pItem) {
        if (!this.mModifierStartedCalled) {
            this.mModifierStartedCalled = true;
            this.onModifierStarted(pItem);
        }
        if (this.mLoopModifierListener != null) {
            this.mLoopModifierListener.onLoopStarted(this, this.mLoop, this.mLoopCount);
        }
    }

    @Override
    public void onModifierFinished(final IModifier<T> pModifier, final T pItem) {
        if (this.mLoopModifierListener != null) {
            this.mLoopModifierListener.onLoopFinished(this, this.mLoop, this.mLoopCount);
        }

        if (this.mLoopCount == LoopModifier.LOOP_CONTINUOUS) {
            this.mSecondsElapsed = 0;
            this.mModifier.reset();
        } else {
            this.mLoop++;
            if (this.mLoop >= this.mLoopCount) {
                this.mFinished = true;
                this.mFinishedCached = true;
                this.onModifierFinished(pItem);
            } else {
                this.mSecondsElapsed = 0;
                this.mModifier.reset();
            }
        }
    }

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================

    public interface ILoopModifierListener<T> {
        public void onLoopStarted(final LoopModifier<T> pLoopModifier, final int pLoop, final int pLoopCount);

        public void onLoopFinished(final LoopModifier<T> pLoopModifier, final int pLoop, final int pLoopCount);
    }
}
